﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using NHibernate.Criterion;
using NHibernate.Sidekick.Data.Domain;
using NHibernate.Sidekick.Data.NHibernate;
using NHibernate.Sidekick.Services.Data;

namespace NHibernate.Sidekick.Services.Interfaces
{
    /// <summary>
    /// Method signatures for the AbstractService."/> methods.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TId">The type of the id.</typeparam>
    public interface IGenericService<T, TId> : IDao<T, TId> { }

    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TId">The type of the id.</typeparam>
    public abstract class AbstractService<T, TId> : IGenericService<T, TId> where T : DomainObject<T, TId>
    {
         /// <summary>
         /// Gets the main data access object.
         /// </summary>
         /// <value>The DAO.</value>
        protected IDao<T, TId> Dao
        {
            get { return new Dao<T, TId>(); }
        }

        /// <summary>
        /// Returns the persistent instance with the specified identity.
        /// </summary>
        /// <param name="id">The instance's identifier.</param>
        /// <param name="shouldLock">If set to <c>true</c>, lock the instace.</param>
        /// <returns>The persistent instance, or null if there is no such persistent instance.</returns>
        public T GetById(TId id, bool shouldLock)
        {
            return Dao.GetById(id, shouldLock);
        }

        /// <summary>
        /// Returns the persistent instance with the specified identity.
        /// </summary>
        /// <param name="id">The instance's identifier.</param>
        /// <returns>The persistent instance, or null if there is no such persistent instance.</returns>
        public T GetById(TId id)
        {
            return Dao.GetById(id);
        }

        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion"/>.
        /// If no <see cref="ICriterion"/> is supplied, this behaves like <see cref="GetAll()"/>.
        /// </summary>
        /// <returns>Every entity available for the requested type.</returns>
        public List<T> GetAll()
        {
            return Dao.GetAll();
        }

        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion"/>.
        /// If no <see cref="ICriterion"/> is supplied, this behaves like <see cref="GetAll()"/>.
        /// </summary>
        /// <param name="isCacheable">if set to <c>true</c> [is cacheable].</param>
        /// <returns>
        /// Every entity available for the requested type.
        /// </returns>
        public List<T> GetAll(bool isCacheable)
        {
            return Dao.GetAll(isCacheable);
        }


        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion"/>.
        /// If no <see cref="ICriterion"/> is supplied, this behaves like <see cref="GetAll()"/>.
        /// </summary>
        /// <param name="maxNumberOfResults">The maxinum number of results.</param>
        /// <param name="firstResult">Accept results following this one.</param>
        /// <param name="isCacheable">if set to <c>true</c> [is cacheable].</param>
        /// <param name="expressions">The filtering expressions.</param>
        /// <returns>The collection of entities.</returns>
        public List<T> GetByCriteria(int? maxNumberOfResults, int? firstResult, bool isCacheable, params Expression<Func<T, bool>>[] expressions)
        {
            return Dao.GetByCriteria(maxNumberOfResults, firstResult, isCacheable, expressions);
        }

        /// <summary>
        /// Loads every instance of the requested type using the supplied <see cref="ICriterion"/>.
        /// If no <see cref="ICriterion"/> is supplied, this behaves like <see cref="GetAll()"/>.
        /// </summary>
        /// <param name="maxNumberOfResults">The maxinum number of results.</param>
        /// <param name="firstResult">Accept results following this one.</param>
        /// <param name="expressions">The filtering expressions.</param>
        /// <returns>The collection of entities.</returns>
        public List<T> GetByCriteria(int? maxNumberOfResults, int? firstResult, params Expression<Func<T, bool>>[] expressions)
        {
            return GetByCriteria(maxNumberOfResults, firstResult, false, expressions);
        }

        /// <summary>
        /// Loads a unique instance of the requested type using the supplied <see cref="ICriterion"/>.
        /// If no <see cref="ICriterion"/> is supplied, this behaves like <see cref="GetAll()"/>.
        /// </summary>
        /// <param name="expressions">The expressions.</param>
        /// <returns>The unique instance.</returns>
        public T GetUniqueByExpression(params Expression<Func<T, bool>>[] expressions)
        {
            return Dao.GetUniqueByExpression(expressions);
        }

        /// <summary>
        /// Loads a unique instance of the requested type using the supplied <see cref="ICriterion"/>.
        /// If no <see cref="ICriterion"/> is supplied, this behaves like <see cref="GetAll()"/>.
        /// </summary>
        /// <param name="isCacheable">if set to <c>true</c> [is cacheable].</param>
        /// <param name="expressions">The expressions.</param>
        /// <returns>The unique instance.</returns>
        public T GetUniqueByExpression(bool isCacheable, params Expression<Func<T, bool>>[] expressions)
        {
            return Dao.GetUniqueByExpression(isCacheable, expressions);
        }

        /// <summary>
        /// Persist the given transient instance, first assigning a generated identifier.
        /// </summary>
        /// <param name="entity">The transient entity.</param>
        /// <returns>The saved instance.</returns>
        public T Save(T entity)
        {
            return Dao.Save(entity);
        }

        /// <summary>
        /// Either <see cref="Save(T)"/> or <see cref="Update(T)"/> the given instance, depending upon resolution of the unsaved-value checks
        /// </summary>
        /// <param name="entity">A transient or detached instance containing new or updated state .</param>
        /// <returns>The updated instance.</returns>
        public T Update(T entity)
        {
            return Dao.Update(entity);
        }

        /// <summary>
        /// Either <see cref="Save(T)"/> or <see cref="Update(T)"/> the given instance, depending upon resolution of the unsaved-value checks
        /// </summary>
        /// <param name="entity">A transient or detached instance containing new or updated state .</param>
        /// <returns>The operated instance.</returns>
        public T SaveOrUpdate(T entity)
        {
            return Dao.SaveOrUpdate(entity);
        }

        /// <summary>
        /// Remove a persistent instance from the datastore. The argument may be an instance associated
        /// with the receiving Session or a transient instance with an identifier associated with existing
        /// persistent state. This operation cascades to associated instances if the association is mapped
        /// with cascade="delete". 
        /// </summary>
        /// <param name="entity">The instance to be removed.</param>
        public void Delete(T entity)
        {
            Dao.Delete(entity);
        }

        /// <summary>
        /// Remove a persistent instance from the datastore. The argument may be an instance associated
        /// with the receiving Session or a transient instance with an identifier associated with existing
        /// persistent state. This operation cascades to associated instances if the association is mapped
        /// with cascade="delete". 
        /// </summary>
        /// <param name="id">The instance's identifier.</param>
        public void Delete(TId id)
        {
            Dao.Delete(id);
        }

        /// <summary>
        /// Flush the associated ISession and end the unit of work.
        /// </summary>
        public void CommitChanges()
        {
            Dao.CommitChanges();
        }

        /// <summary>
        /// Forces the underlying <see cref="ITransaction"/> to roll back.
        /// </summary>
        public void RollBackChanges()
        {
            Dao.RollBackChanges();
        }

        /// <summary>
        /// Re-read the state of the given instance from the underlying database.
        /// </summary>
        /// <param name="entity">The persistent instance.</param>
        public void Refresh(T entity)
        {
            Dao.Refresh(entity);
        }
    }
}
